﻿//////////////////////////////////////////////////////////////////////////////
//
// Copyright © 1998-2024 Glodon Company Limited.
//
// Licensed under the MIT License
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the “Software”),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
//////////////////////////////////////////////////////////////////////////////


#pragma once
#include "ComponentEditorGnufUI.h"
#include "GnufTree.h"
#include "WeakPtr.h"

namespace gcmp
{
    class IUiDocument;
    class IElement;
}
namespace gnuf
{
    class TreeNodeSorter;
}

namespace gfam 
{
    class ExplorerTreeNodeDelegate
    {
        CANNOT_COPY_OR_ASSIGN(ExplorerTreeNodeDelegate)
    public:
        ExplorerTreeNodeDelegate(gnuf::TreeNode* pNode, ExplorerTreeNodeDelegate* parent = nullptr, bool deleteOnEmpty = true);
        ~ExplorerTreeNodeDelegate();

        ExplorerTreeNodeDelegate* addSubNode(const std::wstring& name, bool& existed);
        gnuf::TreeNode* getDelegatedNode() const;
        void unload();

    protected:
        void onSubNodeVisited(ExplorerTreeNodeDelegate* pNode);
        
    private:
        gnuf::TreeNode* m_node;
        bool m_deleteOnEmpty;
        bool m_visited;
        ExplorerTreeNodeDelegate* m_parent;
        std::vector<gcmp::OwnerPtr<ExplorerTreeNodeDelegate>> m_subNodeDelegates;
    };

    class COMPONENT_EDITOR_GNUF_UI_EXPORT ExplorerBlock
    {
        CANNOT_COPY_OR_ASSIGN(ExplorerBlock)
    public:
        ExplorerBlock(const std::wstring& name) : m_blockName(name) {}

        virtual ~ExplorerBlock() {}

        inline std::wstring getBlockName() const { return m_blockName; }
        virtual void refresh(gcmp::IUiDocument*) = 0;
        
        virtual void addSource(gcmp::IElement* src) { m_source.push_back(src); }
        virtual void clearSource() { m_source.clear(); }
        virtual const ExplorerBlock* findSource(const gcmp::IElement* src) const 
        {
            if (std::find(m_source.begin(), m_source.end(), src) != m_source.end()) return this;
            return nullptr;
        }
    protected:
        virtual void loadTreeNodeDelegate() = 0;
        virtual void unLoadTreeNodeDelegate() 
        {
            if (m_parentNodeDelegate.get() != nullptr)
            {
                m_parentNodeDelegate->unload();
            }
            m_parentNodeDelegate = nullptr;

            gcmp::OwnerPtr<gnuf::TreeNodeSorter> opSorter = getSorter();
            if (opSorter != nullptr && m_parentNode.Get() != nullptr)
            {
                m_parentNode->setSorter(opSorter.get());
                m_parentNode->sort(false);
            }
        }
        virtual gcmp::OwnerPtr<gnuf::TreeNodeSorter> getSorter() const { return nullptr; }

    protected:
        std::wstring m_blockName;
        gcmp::WeakPtr<gnuf::TreeNode> m_parentNode;
        gcmp::OwnerPtr<ExplorerTreeNodeDelegate> m_parentNodeDelegate;
        std::vector<gcmp::IElement*> m_source;
    };

    class COMPONENT_EDITOR_GNUF_UI_EXPORT StaticExplorerBlock: public ExplorerBlock
    {
        friend class ExplorerBlockManager;
    public:
        StaticExplorerBlock(const std::wstring& name) : ExplorerBlock(name) {}
        virtual ~StaticExplorerBlock() {}

    protected:
        virtual gnuf::TreeNode* initBlockImpl(gnuf::TreeBoard*) = 0;
        virtual void loadTreeNodeDelegate() override
        {
            DBG_WARN_AND_RETURN_VOID_UNLESS(m_parentNode.Get() != nullptr, L"a block should have one parent node", L"paramodel", L"2024-02-20");
            DBG_WARN_AND_RETURN_VOID_UNLESS(m_parentNodeDelegate == nullptr, L"impossible situation", L"paramodel", L"2024-02-20");

            m_parentNodeDelegate = NEW_AS_OWNER_PTR(ExplorerTreeNodeDelegate, m_parentNode.Get(), nullptr, false);
        }

        void initBlock(gnuf::TreeBoard* board)
        {
            m_parentNode = initBlockImpl(board);
        }
    };

    class DynamicExplorerBlock : public ExplorerBlock
    {
        friend class DynamicExplorerArea;
    public:
        DynamicExplorerBlock(const std::wstring& name) : ExplorerBlock(name) {}
        virtual ~DynamicExplorerBlock() {}

        bool isEmpty() const { return m_parentNode.Get() == nullptr; }

    protected:
        virtual gnuf::TreeNode* initBlockImpl(gnuf::TreeBoard*,const std::vector<gcmp::Any>&) = 0;
        virtual void loadTreeNodeDelegate() override
        {
            DBG_WARN_AND_RETURN_VOID_UNLESS(m_parentNode.Get() != nullptr, L"a block should have one parent node", L"paramodel", L"2024-02-20");
            DBG_WARN_AND_RETURN_VOID_UNLESS(m_parentNodeDelegate == nullptr, L"impossible situation", L"paramodel", L"2024-02-20");

            m_parentNodeDelegate = NEW_AS_OWNER_PTR(ExplorerTreeNodeDelegate, m_parentNode.Get());
            clearSource();
        }

        void initBlock(gnuf::TreeBoard* board, const std::vector<gcmp::Any>& datas)
        {
            m_parentNode = initBlockImpl(board, datas);
        }
    };

    class DynamicExplorerArea
    {
        friend class ExplorerBlockManager;
    public:
        DynamicExplorerArea() {}
        virtual ~DynamicExplorerArea() {}

        const ExplorerBlock* findSource(const gcmp::IElement* src)
        {
            FOR_EACH(block, m_dynamicBlocks)
            {
                if (const ExplorerBlock* pBlock = block->findSource(src)) return pBlock;
            }
            return nullptr;
        }
    protected:
        virtual void initArea(gnuf::TreeBoard* treeBoard) { m_treeBoard = treeBoard; }
        virtual void refreshArea(gcmp::IUiDocument*) = 0;
        virtual void loadDyanmicBlocks()
        {
            FOR_EACH(block, m_dynamicBlocks) block->loadTreeNodeDelegate();
        }
        virtual void unLoadDynamicBlocks()
        {
            FOR_EACH(block, m_dynamicBlocks) block->unLoadTreeNodeDelegate();

            auto itr = m_dynamicBlocks.begin();
            while (itr != m_dynamicBlocks.end())
            {
                if ((*itr)->isEmpty())
                {
                    itr = m_dynamicBlocks.erase(itr);
                }
                else itr++;
            }
        }

        virtual void refresh(gcmp::IUiDocument* pUiDoc)
        {
            refreshArea(pUiDoc);
            FOR_EACH(block, m_dynamicBlocks) block->refresh(pUiDoc);
        }

        template<typename T>
        DynamicExplorerBlock* addOrGetDynamicBlock(const std::wstring& blockName,const std::vector<gcmp::Any>& datas)
        {
            static_assert(std::is_base_of<DynamicExplorerBlock, T>::value, "The added block must be derived from dynamic block.");
            auto itr = std::find_if(m_dynamicBlocks.begin(), m_dynamicBlocks.end(), [&](const gcmp::OwnerPtr<DynamicExplorerBlock>& _one) {
                return _one->getBlockName().compare(blockName) == 0; });

            bool existed = false;
            if (itr != m_dynamicBlocks.end())
            {
                existed = true;
                return itr->get();
            }

            m_dynamicBlocks.emplace_back(NEW_AS_OWNER_PTR(T, blockName));
            DynamicExplorerBlock* rawPointer = m_dynamicBlocks.back().get();
            if (!existed)
            {
                rawPointer->initBlock(m_treeBoard, datas);
                rawPointer->loadTreeNodeDelegate();
            }
            return rawPointer;
        }

    private:
        void clearDynamicBlocks()
        {
            m_dynamicBlocks.clear();
        }

        std::vector<gcmp::OwnerPtr<DynamicExplorerBlock>> m_dynamicBlocks;
        gnuf::TreeBoard* m_treeBoard;
    };

    class COMPONENT_EDITOR_GNUF_UI_EXPORT ExplorerBlockManager final
    {
        CANNOT_COPY_OR_ASSIGN(ExplorerBlockManager)
    public:
        static ExplorerBlockManager* getInstance();

        void initBlocks(gnuf::TreeBoard*);
        void destroyBlocks();
        void refresh(gcmp::IUiDocument* pUiDocument);

        void registerStaticBlock(gcmp::OwnerPtr<StaticExplorerBlock>);
        void registerStaticBlockFront(gcmp::OwnerPtr<StaticExplorerBlock>);
        void removeProjectStaticBlock();
        void registerDynamicArea(gcmp::OwnerPtr<DynamicExplorerArea>);

        const ExplorerBlock* findSourceBlock(const gcmp::IElement* src);
    private:
        ExplorerBlockManager();
        ~ExplorerBlockManager();

        void load();
        void refreshBlocks(gcmp::IUiDocument*);
        void unLoad();

        gnuf::TreeBoard* m_tree;
        std::list<gcmp::OwnerPtr<StaticExplorerBlock>> m_staticBlocks;
        std::list<gcmp::OwnerPtr<DynamicExplorerArea>> m_dynamicAreas;
    };

#define REGISTER_STATIC_EXPLORER_BLOCK(className, blockName) \
    void Register##className() \
    { \
        ExplorerBlockManager::getInstance()->registerStaticBlock(NEW_AS_OWNER_PTR(className, blockName)); \
    } \
    ONE_TIME_INITIALIZER(Register##className);

#define REGISTER_DYNAMIC_EXPLORER_AREA(className) \
    void Register##className() \
    { \
        ExplorerBlockManager::getInstance()->registerDynamicArea(NEW_AS_OWNER_PTR(className)); \
    } \
    ONE_TIME_INITIALIZER(Register##className);
}